import { isFunction,  isObject, isString, queueInvoke } from "@gowiny/js-utils"
import { BeforeEachResult, DebuggerArrayConfig,  NavTarget,  Route, RouteLocationRaw, Router, RouteRule } from "./types"
import qs from 'qs';
import { LifecycleHook, NavType } from "./enums";
import { StaticContext } from "./context";

export function getRouteByPage(page:Page.PageInstance){
    let path = page.route || "";
    if(!path.startsWith("/")){
        path = '/' + path
    }
    const result:Route = {
        fullPath:path,
        path
    }
    return result
}

export function info(router:Router,...args:any[]){
    const logConf = router.options.debugger
    if(logConf && (logConf === true || (logConf as DebuggerArrayConfig).info)){
        console.info(...args)
    }
}
export function error(router:Router,...args:any[]){
    const logConf = router.options.debugger
    if(logConf && (logConf === true || (logConf as DebuggerArrayConfig).info)){
        console.error(...args)
    }
}
export function debug(router:Router,...args:any[]){
    const logConf = router.options.debugger
    if(logConf && (logConf === true || (logConf as DebuggerArrayConfig).info)){
        console.debug(...args)
    }
}
export function warn(router:Router,...args:any[]){
    const logConf = router.options.debugger
    if(logConf && (logConf === true || (logConf as DebuggerArrayConfig).info)){
        console.warn(...args)
    }
}

export function getCurrentPagePath(){
    const page = getCurrentPage()
    return page && page.route ? '/' + page.route : undefined
}
export function getCurrentPage(){
    const pages =  getCurrentPages()
    return pages.length > 0 ? pages[pages.length - 1] : undefined
}

export function invokeHook(vm:any, name:string, args:any[]) {
    const hooks = vm.$[name];
    if(!hooks){
        return
    }
    const fns:Function[] = isFunction(hooks) ? [hooks] : hooks
    let ret;
    for (let i = 0; i < fns.length; i++) {
        ret = fns[i].call(vm,...args)
    }
    return ret;
}



export function getPathByFullPath(fullPath:string){
    let index = fullPath.indexOf("?");
    let result = index == -1 ? fullPath : fullPath.substring(0,index);
    return result;
}

export function getQueryByFullPath(fullPath:string){
    let index = fullPath.indexOf("?");
    let result = index == -1 ? {} : qs.parse(fullPath.substring(index + 1));
    return result;
}

export function getPathAndQueryByFullPath(fullPath:string){
    let index = fullPath.indexOf("?");
    let path:string,query:Record<string,any>;
    if(index  != -1){
        path = fullPath.substring(0,index)
        let queryString = fullPath.substring(index + 1)
        query = queryString ? qs.parse(queryString) : {}
    }else{
        path = fullPath
        query = {}
    }
    return {path,query};
}


export function getRouteByPath(router:Router,path:string,query:any,fullPath?:string){
    fullPath = fullPath || formatFullPath(path,query)
    const key = getPathByFullPath(path);
    const routeRule = router.routeMap.pathMap[key]
    let result
    if(routeRule){
        result = {...routeRule ,fullPath,path,query : query}
    }else{
        result = {fullPath,path,query : query}
    }
    return result
}

export function getRouteByUrl(url:string,router:Router){
    const index = url.indexOf('?')
    let queryString,path;
    if(index > -1){
        path = url.substring(0,index)
        queryString = url.substring(index+1)
    }else{
        path = url
        queryString = ''
    }
    const query = queryString ? qs.parse(queryString) : {}
    //const key = path.replace(/^\//,'')
    const key = path;
    const routeRule = router.routeMap.pathMap[key]
    let result
    if(routeRule){
        result = {...routeRule ,fullPath:url,path,query}
    }else{
        result = {fullPath:url,path,query}
    }
    return result
}

export function formatFullPath(path:string,query:any){
    const queryString = typeof(query)=== 'string' ? query : qs.stringify(query)
    const fullPath = queryString ? `${path}?${queryString}` : path
    return fullPath
}


export function lockNavjump(to:RouteLocationRaw,router:Router,navType:NavType,force:boolean=false){
    let route:RouteRule
    let path:string;
    let fullPath:string;
    const toParam:any = {}
    
    if(isString(to)){
        let path:string
        let toStr = to as string;
        fullPath = toStr;
        let index = toStr.indexOf("?");
        if(index > -1){
            path = toStr.substring(0,index);
        }else{
            path = toStr;
        }
        const pathMap = router.routeMap.pathMap
        route = pathMap[path]
    }else{
        const toObj = to as any
        let query:any;
        if(toObj.name){
            const nameMap = router.routeMap.nameMap
            route = nameMap[toObj.name]
            path = route.path
            query = toObj.query || toObj.params
        }else{
            const pathMap = router.routeMap.pathMap
            path = toObj.path
            query = toObj.query;
            let index = path.indexOf("?");
            if(index != -1){
                let otherQuery = path.substring(index+1);
                if(otherQuery){
                    if(!query){
                        query = otherQuery
                    }else{
                        query += "&" + otherQuery
                    }
                }
                path = path.substring(0,index)
            }
            route = pathMap[path]
        }
        fullPath = formatFullPath(path,query)
        const otherParams = {...toObj}
        delete otherParams.name
        delete otherParams.path
        delete otherParams.query
        delete otherParams.params
        Object.assign(toParam,otherParams)
    }
    let isTab:boolean = route.isTab;
    toParam.url = fullPath
    toParam.$force = force
    let result
    switch(navType){
        case NavType.PUSH :
            if(isTab){
                result = uni.switchTab(toParam)
            }else{
                result = uni.navigateTo(toParam)
            }
            break;
        case NavType.REPLACE :
            if(isTab){
                result = uni.switchTab(toParam)
            }else{
                result = uni.redirectTo(toParam)
            }
            break;
        case NavType.PUSH_TAB :
            if(isTab){
                result = uni.switchTab(toParam)
            }else{
                result = uni.navigateTo(toParam)
            }
            break;
        case NavType.REPLACE_ALL :
            if(isTab){
                result = uni.switchTab(toParam)
            }else{
                result = uni.reLaunch(toParam)
            }
            break;
        default :
            throw new Error('路由类型不正确')
    }
    return result
}


function appendPages(routes:RouteRule[],pathMap:Record<string,RouteRule>,tabBarItemMap:Record<string,PageTabBarItem>,pages:any[],pageRoot?:string){
    let pathPrefix = pageRoot ? '/' + pageRoot  : '';
    pages.forEach(item=>{
        const tempPath = item.path as string
        const path = pathPrefix + (tempPath.startsWith('/') ? tempPath : '/' + tempPath)
        let isTab = tabBarItemMap[path] ? true : false
        const route:RouteRule = {
            ...item,
            isTab,
            path
        }
        pathMap[path] = route
        routes.push(route)
    })
}


interface PageTabBarItem{
    pagePath:string,
    text:string
}
interface PageTabBar{
    list:PageTabBarItem[]
}

export function parseRoutesFromPages({pages,subPackages,tabBar}:{pages:any[],subPackages?:any[],tabBar?:PageTabBar}){
    const routes:RouteRule[] = []
    const pathMap:Record<string,RouteRule> = {}
    const tabItemMap:Record<string,PageTabBarItem> = {}
    if(tabBar){
        tabBar.list.forEach(item=>{
            let path = "/" + item.pagePath;
            tabItemMap[path] = item
        })
    }
    appendPages(routes,pathMap,tabItemMap,pages)
    if(subPackages && subPackages.length > 0){
        subPackages.forEach(item=>{
            appendPages(routes,pathMap,tabItemMap,item.pages,item.root)
        })
    }
    return routes
}



export async function callEachHooks(router:Router, hookType:LifecycleHook, to:Route,from?:Route):Promise<BeforeEachResult> {
    let hooks = router.lifeCycleHooks[hookType]
    const result = await queueInvoke(hooks,null,[to,from],(res)=>{
        if(res === false || isObject(res)){
            return false
        }else{
            return true
        }
    })
    return result
}


export async function invokeAfterEach(router:Router, to:Route,from?:Route) {
    return await callEachHooks(router,LifecycleHook.AFTER_EACH,to,from)
}

export async function invokeBeforeEach(router:Router, to:Route,from?:Route) {
    StaticContext.beforeEachLock = true
    try{
        const hookResult = await callEachHooks(router,LifecycleHook.BEFORE_EACH,to,from)
        if(hookResult === true || hookResult === undefined || hookResult === null){
            return true;
        }else{
            if(hookResult !== false){
                let to:RouteLocationRaw | undefined;
                let navType:NavType= NavType.PUSH;
                if(isString(hookResult)){
                    to = {path:hookResult as string};
                    navType = NavType.PUSH;
                }else if(isObject(hookResult)){
                    let tempNavType;
                    if((<any>hookResult).to){
                        const navTarget = hookResult as NavTarget
                        to = navTarget.to;
                        tempNavType = navTarget.navType;
                    }else{
                        to = hookResult as RouteLocationRaw ;
                        tempNavType = (<any>hookResult).navType;
                    }
                    if(tempNavType){
                        navType = tempNavType;
                    }
                    
                }
                if(to){
                    lockNavjump(to, router, navType,true);
                }
            }
            return false
        }
    }finally{
        StaticContext.beforeEachLock = false
    }
}

